#!/bin/bash
# 
# DataXone interim Patch Installer 
#
#
# @Author : liaobaikai@dsgdata.com
# @Date   : 2023-09-23: 1.0.0
# @Update : 2023-10-08: 1.0.1
#	    1) Support JSON attribute updated
#	    2) Get db_host, db_port, db_user, db_pass from Install DataXone.
# @Update : 2023-10-10: 1.0.2
#          1) fix bug
# @Update : 2023-10-18: 1.1.1
#          1) fix bug
# @Update : 2023-11-10: 1.1.2
#	   1) add lsinventory ...
#	   2) add rollback ...
#	   3) bugs fixed
# @Update : 2023-11-16: 1.1.4
#          1) remove suffix [/] of the dataxone_base
#          2) precheck dept tools.
# @Update : 2024-01-12: 1.1.6
#          1) fixed bug: +files/
#          2) format output logs.
#          3) fixed bug: model=apps with rollback.
# @Update : 2024-01-17: 1.1.7
#          1) fixed bug: Prerequisite check.
#          2) fixed bug: sqlfiles/: USE statement.
# @Update : 2024-01-18: 1.1.8
#          1) future: add CHECKSUM .
# @Update : 2024-01-18: 1.1.10
#          1) future: compare --db-host and config.jdbc.url

# @Version: 1.1.10
#
# ----------------------------------
version=1.1.10
dataxone_basedir=${ARGV_PARAM_PATCH_DATAXONE_BASE:-/data/dataxone}
patchset_file=${ARGV_PARAM_PATCH_PATCH_FILE}
DATAXONE_INVENTORY_DBNAME="dataxone_inventory"
MYSQL_HOME=$SCRIPT_HOME/embed/mysql/$(uname -i)

_ARGV_PARAM_DATAXONE_DB_HOST=$ARGV_PARAM_DATAXONE_DB_HOST
_ARGV_PARAM_DATAXONE_DB_PORT=$ARGV_PARAM_DATAXONE_DB_PORT
_ARGV_PARAM_DATAXONE_DB_USER=$ARGV_PARAM_DATAXONE_DB_USER
_ARGV_PARAM_DATAXONE_DB_USER_PASSWORD=$ARGV_PARAM_DATAXONE_DB_USER_PASSWORD
ARGV_PARAM_PATCH_SKIP_CHECKSUM=1

######### inventory #########
dataxone_inventory=$dataxone_basedir/inventory

function findWebapps(){
	app=$1

	path=$(egrep "^$app:" $dataxone_inventory/xoneInventory.txt 2>/dev/null | awk -F ':' '{print $2}')
	if [ -n "$path" ]; then
		echo $path
		return
	fi
	
	matched_text=$(egrep "path=\"/$app\"" $dataxone_basedir/*omcat/conf/server.xml 2>/dev/null)
	if [ -n "$matched_text" ]; then
		echo "$matched_text" | egrep -o "docBase=.*" | awk '{print $1}' | sed -e 's/docBase=//' -e 's/"//g'
	else
		find $dataxone_basedir/*omcat/webapps -name "$app" -type d | egrep -v 'xpatch|Catalina|maven|Patch_'
	fi
}

function get_value(){
	awk -F '=' '{for(i=2;i<=NF;i++)s=s"="$i;}END{print s}' | sed 's/=[ ]*//' | awk '{print $1}'
}

function abort(){
	if [ -n "$ARGV_PARAM_PATCH_FORCE" ]; then
		error "User Responded with: --force. Skip line $base_path/$line’" && error_count=$(expr $error_count + 1) && return
	fi

        echo "Aborting" && \
                error_count=$(expr $error_count + 1) && \
                completed && \
                exit -1
}

function completed(){
        echo
	echo "--------------------------------------------------------------------------------"
	echo 
        printf "$(basename $0) completed"

        if [ $error_count -ne 0 ]; then
                echo " with errors."
        elif [ $warn_count -ne 0 ]; then
                echo " with warnings."
        else
                echo "."
        fi
        echo

}


function __now__(){
        date +'%Y-%m-%d %H:%M:%S'
}

calc_webapps_sha256sum(){
        cd $1/WEB-INF/classes && tar -cvf checksum.tar.gz com mybatis 1>/dev/null 2>&1
        sha256sum checksum.tar.gz | awk '{print $1}'  && rm checksum.tar.gz
}

calc_web_sha256sum(){
        cd $1/static && tar -cvf checksum.tar.gz js 1>/dev/null && sha256sum checksum.tar.gz | awk '{print $1}'  && rm checksum.tar.gz
}


function out(){
        printf "$(__now__) [INFO] "
        [ -z "$module" ] && printf "$1" || printf "[%s] %s" "${module:0:6}" "$1"
}

function ok(){
        echo " OK."
}

function info(){
        printf "$(__now__) [INFO] "
        [ -z "$module" ] && echo "$1" || echo "[${module:0:6}] $1"
}

function error(){
        printf "\x1b[31m$(__now__) [ERROR] "
        [ -z "$module" ] && printf "$1\x1b[0m\n" || printf "[${module:0:6}] $1\x1b[0m\n"
        error_count=$(expr $error_count + 1)
}

function warn(){
        printf "\x1b[33m$(__now__) [WARN] "
        [ -z "$module" ] && printf "$1\x1b[0m\n" || printf "[${module:0:6}] $1\x1b[0m\n"
        warn_count=$(expr $warn_count + 1)
}


############################################################################
## db_host, db_port, db_user, db_pass from 
URL=mysqlmonitor.jdbc.url
USER=mysqlmonitor.jdbc.username
PASS=mysqlmonitor.jdbc.password

CRON_DOCKER_IP=docker.ip
CRON_DOCKER_PORT=docker.port

OLD_IFS=$IFS
IFS=''
conf=$dataxone_basedir/DataXone_Query_Engine/query_db_engine/config/module/properties/jdbc.properties
[ ! -f $conf ] && error "readfile: $conf: No such file" && abort
APP_PROPERTIES=$(egrep "$URL|$USER|$PASS" $conf | grep -v '[ ]*#')
for line in $APP_PROPERTIES ; do
        db_host_port=$(echo "$line" | grep "$URL" | egrep -o '//.*/' | sed -e 's/\///g')
        if [ -n "$db_host_port" ]; then
                db_host=$(echo "$db_host_port" | awk -F ':' '{print $1}')
                db_port=$(echo "$db_host_port" | awk -F ':' '{print $2}')
        fi
        db_user=$(echo "$line" | grep "$USER" | awk -F '=' '{print $NF}' | awk '{print $1}')
        db_pass=$(echo "$line" | grep "$PASS" | get_value)

done

IFS=$OLD_IFS

db_port=${db_port:-3306}

############################################################################


export ARGV_PARAM_DATAXONE_DB_HOST=${ARGV_PARAM_DATAXONE_DB_HOST:-$db_host}
export ARGV_PARAM_DATAXONE_DB_USER=${ARGV_PARAM_DATAXONE_DB_USER:-$db_user}
export ARGV_PARAM_DATAXONE_DB_PORT=${ARGV_PARAM_DATAXONE_DB_PORT:-$db_port}
export ARGV_PARAM_DATAXONE_DB_USER_PASSWORD=${ARGV_PARAM_DATAXONE_DB_USER_PASSWORD:-$db_pass}

export DB_HOST=$ARGV_PARAM_DATAXONE_DB_HOST
export DB_USER=$ARGV_PARAM_DATAXONE_DB_USER
export DB_PORT=$ARGV_PARAM_DATAXONE_DB_PORT
export DB_PASS=$ARGV_PARAM_DATAXONE_DB_USER_PASSWORD

export BASEDIR=$dataxone_basedir
dir1=$(findWebapps "autoMaticEngineBoot-1.0.0")
web_xone_conf=$dir1/WEB-INF/classes/application.properties

## mapping hostname
host_port=$(egrep "module.path" $web_xone_conf 2>/dev/null | grep -v '[ ]*#' | egrep -o '//.*/' | awk -F '/' '{print $3}' | sed -e 's/\///g')
[ $? -ne 0 ] && abort

export HOSTNAME=$(echo "$host_port" | awk -F ':' '{print $1}')
export PORT=$(echo "$host_port" | awk -F ':' '{print $2}')
## ip addr
export IPADDR=$(egrep "module.yxad.service.ip_address" $web_xone_conf 2>/dev/null | grep -v '[ ]*#' | get_value)
[ $? -ne 0 ] && abort

export DB_PASS_ENCODED=$(egrep "spring.datasource.password" $web_xone_conf 2>/dev/null | grep -v '[ ]*#' | get_value)
export OS_USER=$(egrep -i "ftp_collection_userName" $web_xone_conf 2>/dev/null | grep -v '[ ]*#' | get_value)
export OS_USER_PASSWORD=$(egrep -i "ftp_collection_password" $web_xone_conf 2>/dev/null | grep -v '[ ]*#' | get_value)

#
dir1=$(findWebapps "DSGWEB")
web_base_path=$dir1
web_host_port=$(python $SCRIPT_HOME/json-search-master.py $dir1/static/globalParams.json communicationAddressTest | egrep -o '//.*/' | awk -F '/' '{print $3}' | sed -e 's/\///g')
[ $? -ne 0 ] && abort

export WEB_HOSTNAME=$(echo "$web_host_port" | awk -F ':' '{print $1}')
export WEB_PORT=$(echo "$web_host_port" | awk -F ':' '{print $2}')

#### old version ?:
#### old_version=$(grep '__version__:' $dataxone_inventory/xoneInventory.txt 2>/dev/null | sed 's/__version__://')

export ALLPY_VERSION=$(python $SCRIPT_HOME/json-search-master.py $dir1/static/globalParams.json version)

##### __DATAXONE_XPATCH_LS_INVENTORY__ end...

if [ -n "$patchset_file" ]; then

	#########
	# get uncomparess directory name
	#########
	mkdir -p $SCRIPT_HOME/tmp/.0000 && \
	\cp $patchset_file $SCRIPT_HOME/tmp/.0000 && \
	cd $SCRIPT_HOME/tmp/.0000 && \
	tar -zxf $SCRIPT_HOME/tmp/.0000/*.tar.gz && \
	patchset_dirname=$(ls -lt $SCRIPT_HOME/tmp/.0000/ | grep ^d | awk '{print $NF}') && \
	\cp -R $SCRIPT_HOME/tmp/.0000/$patchset_dirname $SCRIPT_HOME/tmp && \
	\rm -rf $SCRIPT_HOME/tmp/.0000
	#########

	# after build patchset directory
	patchset_dir=$SCRIPT_HOME/tmp/$patchset_dirname
else
	patchset_dir=$PWD
fi
cd $patchset_dir
export CURRDIR=$patchset_dir

######### inventory #########
backup_dir=$dataxone_inventory/backup/xpatch
inventory_file=$dataxone_inventory/inventory.props

export MYSQL_PWD="${ARGV_PARAM_DATAXONE_DB_USER_PASSWORD}"
ACTION_ARCHIVED="archived"
ACTION_APPLY="apply"
warn_count=0
error_count=0
patch_count=0

mkdir -p $dataxone_inventory
mkdir -p $backup_dir
[ ! -f $inventory_file ] && touch $inventory_file


option_module_cron=0
option_module_cron_plugin=0
option_module_manager=0
option_modules=

##### option modules ?
# Option module is installed ?
path=$(findWebapps "cron-autoMaticEngineBoot-1.0.0")
if [ -n "$path" ] ; then
	option_module_cron=1
	option_modules=" CronTomcat"
	conf=$path/WEB-INF/classes/application.properties

	host_port=$(egrep "module.path" $conf 2>/dev/null | grep -v '[ ]*#' | egrep -o '//.*/' | awk -F '/' '{print $3}' | sed -e 's/\///g')
	[ $? -ne 0 ] && abort
	
	export CRON_HOSTNAME=$(echo "$host_port" | awk -F ':' '{print $1}')
	export CRON_PORT=$(echo "$host_port" | awk -F ':' '{print $2}')

	export CRON_DOCKER_IP=$(egrep "$CRON_DOCKER_IP" $conf 2>/dev/null | grep -v '[ ]*#' | get_value)
	export CRON_DOCKER_PORT=$(egrep "$CRON_DOCKER_PORT" $conf 2>/dev/null | grep -v '[ ]*#' | get_value)
	
	if [ -d $dataxone_basedir/quartz_http_interface_jar ] ; then
		option_module_cron=2
	fi	
fi
[ -d $dataxone_basedir/Manager_Node_Engine ] && option_module_manager=1 && option_modules="${option_modules} Manager_Node_Engine"



# Backup table with patch.
#
# --------------------------------
function queryTableFromFile(){
	
	awk 'BEGIN{IGNORECASE=1;db="";tab=""}{
    if(($0 !~ /^\/\*.*\*\// && $0 !~ /^-/) && ($0~/USE\s+.*;/)){
        db=$2
        p1=index(db,";")
        if(p1>0){db=substr(db,0,p1-1)}
    }
    if( ($0 !~ /^\/\*.*\*\// && $0 !~ /^-/) && ($0~/^[\s+]?(INSERT|REPLACE)\s+INTO\s+/ || $0~/^[\s+]?DELETE\s+FROM\s+/ || $0~/^[\s+]?(CREATE|ALTER)\s+TABLE\s+/)) {
        tab=$3
        p1=index(tab, "(");
        if(p1>0){tab=substr(tab,0,p1-1)}
        p2=index(tab, ".");
        if(p2>0){db=substr(tab,0,p2-1);tab=substr(tab,p2+1)}
        print db " " tab
    } else if(($0!~/^\/\*.*\*\// && $0!~/^-/) && $0~/^[\s+]?UPDATE.*\s+SET/){
        tab=$2
        p1=index(tab, "(");
        if(p1>0){tab=substr(tab,0,p1-1)}
        p2=index(tab, ".");
        if(p2>0){db=substr(tab,0,p2-1);tab=substr(tab,p2+1)}
        print db " " tab
    }
}' "$1" | sed 's/`//g' | sort | uniq


}

# 
# backup database by mysqldump
# patch database by mysql client.
# 
function backupAndPatchDB(){

	sql_file="$2"

	DB_NAMES="automated_screen_theme_report_db permission_dsg data_xcmp automation_dsg_db_new dataxone_bigdatas_db dataxone_pmon"

	if [ $1 == "$ACTION_ARCHIVED" ]; then
		today=$(date '+%F_%H-%M-%S')
		db_backup_dir=$backup_dir/$patch/sqlfiles
		mkdir -p $db_backup_dir
		
		echo
		info "Backing up databases, sqlfile $sql_file ..."

		# rollback sql script segment
		echo "
$MYSQL_HOME/bin/mysql -f -h${ARGV_PARAM_DATAXONE_DB_HOST} -u${ARGV_PARAM_DATAXONE_DB_USER} -P${ARGV_PARAM_DATAXONE_DB_PORT:-3306} <<EOSQL
" >> $patch_rollback_file
		
		for db_name in $DB_NAMES; do
		
			tables=$(queryTableFromFile $sql_file | grep "^${db_name} " | awk '{print $2}' | tr '\n' ' ')
			[ -z "$tables" ] && continue
		
			sql_where=$(echo "$tables" | sed -e "s/^/('/" -e "s/ /','/g" -e "s/$/'\);/")
			sql="SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA = '$db_name' and TABLE_NAME in $sql_where"
			
			db_tables=$($MYSQL_HOME/bin/mysql -s -h${ARGV_PARAM_DATAXONE_DB_HOST} -u${ARGV_PARAM_DATAXONE_DB_USER} -P${ARGV_PARAM_DATAXONE_DB_PORT:-3306} -e "$sql")
			
			info "Backing up database $db_name ..."
			mkdir -p $db_backup_dir/${db_name}

			# rollback sql script segment
			echo "use ${db_name};" >> $patch_rollback_file
			
			for table in $tables; do

				[ "=" == "$table" ] && continue
			
				backup_sql_file=$db_backup_dir/${db_name}/${table}.sql
				
				# check table in db ?
				is_exists=$(echo "$db_tables" | tr ' ' '\n' | grep "^$table$" | wc -l)
				
				[ $is_exists -eq 0 ] && echo "DROP TABLE IF EXISTS ${db_name}.${table};" >> $patch_rollback_file && continue
			
				printf "$(__now__) [INFO] Backing up table ${db_name}.${table} to $backup_sql_file ..."
				
				$MYSQL_HOME/bin/mysqldump --set-gtid-purged=OFF --no-tablespaces --single-transaction --routines --events --triggers \
					-h${ARGV_PARAM_DATAXONE_DB_HOST} -u${ARGV_PARAM_DATAXONE_DB_USER} -P${ARGV_PARAM_DATAXONE_DB_PORT:-3306} \
					$db_name $table > $backup_sql_file

				[ $? -ne 0 ] && abort || ok

				# rollback sql script segment
				echo -e "drop table if exists ${db_name}.${table};\n\. $backup_sql_file" >> $patch_rollback_file
					
				
			done

			info "Backed up database ${db_name}."
			
		done
		
		echo "EOSQL" >> $patch_rollback_file
		info "Backed up databases, sqlfile $sql_file."
		
	elif [ $1 == "$ACTION_APPLY" ]; then
	
		echo "Executing SQL file $sql_file ..."

		$MYSQL_HOME/bin/mysql -f -s -h${ARGV_PARAM_DATAXONE_DB_HOST} -u${ARGV_PARAM_DATAXONE_DB_USER} -P${ARGV_PARAM_DATAXONE_DB_PORT:-3306} <<EOSQL
source $sql_file;
exit
EOSQL
		info "Upgraded database: SQL file $sql_file successfully executed."
		
	fi

}



function build_etc(){
	file1="$1"
	file2="$2"

	new_file1="$file1.swp2"
	fin_file1="$file1.swp3"

	echo "
cat > $fin_file1  <<EOF
$(cat $file1)
EOF
" > $new_file1

	cat $new_file1 | sh 
	\cp $fin_file1 $file2
	rm $new_file1 $fin_file1 2>/dev/null
}


# Backup & patch dataxone.
# 
# Patch by <patch>/updates.lst
# action=ACTION_ARCHIVED: Backup files.
# action=ACTION_APPLY   : Patch files.
# -------------------------------------
function applyPatch(){
	
	action="$1"

	for patch in $(ls -l $patchset_dir | sort -k9 | grep ^d | awk '{print $NF}'); do
		cd $CURRDIR
		export CURR_PATCH=$patch

		### read patch dir updates.lst
		if [ ! -f $patch/updates.lst ]; then
			warn "Patch $patch miss updates.lst file, `pwd`/$patch/updates.lst No such file, skip patch."
			continue
		fi

		#if [ ! -f $patch/CHECKSUM ]; then
		#	warn "Patch $patch miss CHECKSUM file, skip patch."
		#	continue
		#fi

		# check patch...
		is_patched=$(egrep "^${patch}=" $inventory_file)
		if [ -n "$is_patched" -a -z "$ARGV_PARAM_PATCH_FORCE_REPLACE" ]; then
			patched_date=$(egrep "^@${patch}=" $inventory_file | sed -e "s/^@${patch}=//" -e "s/###.*$//")
			info "Patch $patch: applied on $patched_date"
			echo
			continue
		fi

		# User Responded...
		if [ -z "$user_rsp" ]; then
			try_times=0
			until [ $try_times -ge 3 ]; do
				echo "Is the local system ready for patching? [y|n] "
				read user_rsp
				echo "User Responded with: $user_rsp"
				[ "n" == "$user_rsp" -o "N" == "$user_rsp" ] && completed && exit 0
				[ "y" == "$user_rsp" -o "Y" == "$user_rsp" ] && echo && break
				try_times=$(expr $try_times + 1)
			done
		fi
		[ "y" != "$user_rsp" -a "Y" != "$user_rsp" ] && echo -e "\nBye." && exit -1

		[ $patch_count -eq 0 ] && info "Backing up files..."

		base_path=$patchset_dir/$patch
		export BASE_PATH=$base_path
		patch_backup_dir=$backup_dir/$patch
		mkdir -p $patch_backup_dir
		export patch_rollback_file=$patch_backup_dir/rollback.sh
		[ $1 == "$ACTION_ARCHIVED" ] && echo -e "export MYSQL_PWD=\"${ARGV_PARAM_DATAXONE_DB_USER_PASSWORD}\"\nset -x\n" > $patch_rollback_file
			
		echo
		[ $1 == "$ACTION_ARCHIVED" ] && info "Backing up files affected by the patch $patch for recovery, this operation will take some time ..." || info "Applying patch $patch ..."
		patch_count=$(expr $patch_count + 1)

		export ALLPY_VERSION=$(python $SCRIPT_HOME/json-search-master.py $web_base_path/static/globalParams.json version)

		while read line ; do
			cd $CURRDIR
			# version
			patch_version=$(echo $line | grep "^#!version/")
			if [ -n "$patch_version" ]; then
				patch_version_num=$(echo $patch_version | sed -e 's/#!version\///' -e 's/\.//g')
				current_version=$(echo $ALLPY_VERSION | sed -e 's/DataXone-//' -e 's/\.//g')
				if [ $current_version -ge $patch_version_num ]; then
					info "Current version $ALLPY_VERSION, patch version is $(echo $patch_version | sed -e 's/#!version\//DataXone-/'), skip patch."
					break
				fi
				continue
			fi
			
			comment_line=$(echo $line | grep '^#')
			[ -n "$comment_line" ] && continue

			blank_line=$(echo $line | egrep '^[\s]*$' | wc -l)
			[ $blank_line -ne 0 ] && continue

			case $line in 
				*/DataXone_Query_Engine/*) module=$(echo "$line" | awk -F '/' '{dir=""; for(i=2;i<=3;i++)dir=dir"/"$i}END{print substr(dir,2)}') ;;
				apps/*) module=$(echo "$line" | awk -F '/' '{dir=""; for(i=2;i<NF;i++)dir=dir"/"$i}END{print substr(dir,2)}') ;;
				sqlfiles/*) ;;
				\?*) unset module ;;
				*) module=$(echo "$line" | awk -F '/' '{print $2}') ;;
			esac

			option_not_exists=0
			case $module in 
			cron-autoMaticEngineBoot-*) [ $option_module_cron -eq 0 ]    && option_not_exists=1 ;;
			quartz_http_interface_jar)  [ $option_module_cron -ne 2 ]    && option_not_exists=2 ;;
			Manager_Node_Engine)        [ $option_module_manager -eq 0 ] && option_not_exists=1 ;;
			esac

			if [ $option_not_exists -eq 1 ]; then
				[ $1 == "$ACTION_ARCHIVED" ] && \
				warn "Backing up directory $module files: Component not installed. Skip line “$line”." || \
				warn "Updating directory $module files: Component not installed. Skip line “$line”."

				continue
			elif [ $option_not_exists -eq 2 ]; then
				[ $1 == "$ACTION_ARCHIVED" ] && \
				warn "Backing up directory $module files: Component not installed. Skip line “$line”." && \
				continue
			fi

			if [ $1 == "$ACTION_APPLY" ]; then
				if [ -n "$(echo "$line" | grep '^@')" ]; then
					unset module && out "Executing scripts: "
				elif [ -n "$(echo "$line" | grep '^sqlfiles/')" ]; then
					unset module && out "Patching database: "
				elif [ -n "$(echo "$line" | grep '^? ')" ]; then
					unset module
					echo "$line" | cut -c '3-' | bash
					[ $? -ne 0 ] && abort
					continue
				fi
			fi

			case $line in
			@custom/*)
				# scripts
				[ $1 == "$ACTION_ARCHIVED" ] && continue

				script_file=$(echo "$line" | sed 's/^@//')
				echo "'$base_path/$script_file'"

				eval $(echo "sh '$base_path/$script_file'")
				continue
				;;
			wars/*)
				
				dir1=$(findWebapps "$module")

				if [ $1 == "$ACTION_ARCHIVED" ] ; then

					if [ -z "$dir1" ]; then
						warn "Backing up directory $module files: Component not installed. Skip line “$line”."
						continue
					fi

					mkdir -p $patch_backup_dir/wars
					[ $? -ne 0 ] && abort
					out "Backing up directory $dir1 to $patch_backup_dir/wars ..."

					\cp -R $dir1 $patch_backup_dir/wars/
					
					[ $? -ne 0 ] && abort || ok


				elif [ $1 == "$ACTION_APPLY" ]; then

					if [ -z "$dir1" ]; then
                                                warn "Upgrading with ${base_path}/${line}: Component not installed. Skip line “$line”."
                                                continue
                                        fi

					echo "$module:$dir1" >> $SCRIPT_HOME/tmp/do_checksum.txt

					# unzip current dir
					out "Upgrading with ${base_path}/${line} ..."
					# build rollback scripts.
					echo "### $line" >> $patch_rollback_file

					\rm -rf $dir1
					[ $? -ne 0 ] && abort
					echo "\rm -rf $dir1" >> $patch_rollback_file
					
					unzip -oq ${base_path}/${line} -d $dir1
										
					[ $? -ne 0 ] && abort || ok

					# build rollback scripts.
                                        echo "\cp -R $patch_backup_dir/wars/$(basename $dir1) $(dirname $dir1)" >> $patch_rollback_file

				fi
				
				;;
				
			apps/*)
				
				if [ $1 == "$ACTION_ARCHIVED" ] ; then

					if [ ! -d "$dataxone_basedir/$module" ]; then
						warn "Backing up directory $module files: Component not installed. Skip line “$line”."
						continue
					fi
				
					mkdir -p $patch_backup_dir/apps/$module
					[ $? -ne 0 ] && abort
					out "Backing up directory $module to $patch_backup_dir/apps ..."

					\cp -R $dataxone_basedir/$module $patch_backup_dir/apps/$module
					
					[ $? -ne 0 ] && abort || ok


				elif [ $1 == "$ACTION_APPLY" ]; then
					
					echo "$module:$dataxone_basedir/$module" >> $SCRIPT_HOME/tmp/do_checksum.txt
					
					out "Upgrading with ${base_path}/${line} ..."
					echo "### $line" >> $patch_rollback_file

					\rm -rf $dataxone_basedir/$module 2>/dev/null
					[ $? -ne 0 ] && abort
					echo "\rm -rf $dataxone_basedir/$module 2>/dev/null" >> $patch_rollback_file
					
					cd $(dirname $dataxone_basedir/$module) && \
					tar -zxf ${base_path}/${line}
										
					[ $? -ne 0 ] && abort || ok

					# build rollback scripts.
					nf=$(echo "$module" | awk -F '/' '{print NF}')
					_m=""
					if [ $nf -gt 1 ]; then
						_m=$(dirname $module)
					fi  
					echo "\cp -R $patch_backup_dir/apps/$module/* $dataxone_basedir/$_m" >> $patch_rollback_file
					unset nf _m
					
				fi
				
				;;
			\!etc/*|+etc/*|-etc/*|etc/*)
				# new file path
				new_file_path=$(echo "$line" | sed 's/^[!+-]*etc\///')
				
				is_sub_module=$(echo "$module" | grep '/' | wc -l)
				if [ $is_sub_module -eq 1 ]; then
					#old_file=$dataxone_basedir/$module/$new_file_path  BUG
					old_file=$dataxone_basedir/$new_file_path
				else
					case $module in
					Manager_Node_Engine|yxad.*|quartz_http_interface_jar|regd) dir1=$dataxone_basedir/$module ;;
					*) dir1=$(findWebapps "$module") ;;
					esac

					[ -z "$dir1" ] && dir1=$(find ${dataxone_basedir}/ -name "$module" -type d | egrep -v 'xpatch|Catalina|maven|Patch_')
					[ -z "$dir1" ] && warn "Upgrading with $new_file_path: Component not installed. Skip line “$line”." && continue
					
					dir0=$(dirname $dir1)
					
					old_file=$dir0/$new_file_path
				fi
				
				if [ $1 == "$ACTION_ARCHIVED" ] ; then
					file_name=$(basename $old_file)
					if [ ! -f $patch_backup_dir/etc/$module/$file_name ]; then 
						out "Backing up file $old_file to $patch_backup_dir/etc/$module ..."
						mkdir -p $patch_backup_dir/etc/$module
						[ $? -ne 0 ] && abort
						\cp $old_file $patch_backup_dir/etc/$module
						[ $? -ne 0 ] && abort || ok
					else
						info "Backing up file '$old_file': File exists. Skip line “$line”"
					fi
					continue
				fi
				
				new_file=${base_path}/etc/${new_file_path}

				case $line in
				\!*) out "Overwrite file $old_file ..." ;;
				*) out "Updating file $old_file ..." ;;
				esac
				
				case $line in
				*.json)
					case $line in
					# Use backup_file replace old_file on module
					\!etc/*) \cp $patch_backup_dir/etc/$module/$(basename $old_file) $old_file ;;
					+etc/*) $SCRIPT_HOME/json_attr_master.sh $old_file $new_file ;;
					-etc/*) $SCRIPT_HOME/json_attr_master.sh $old_file $new_file "remove" ;;
					etc/*) build_etc $new_file $old_file ;;
					esac
					;;
				*.properties)
					case $line in
                                        # Use backup_file replace old_file on module
                                        \!etc/*) \cp $patch_backup_dir/etc/$module/$(basename $old_file) $old_file ;;
					# ./config_master.sh old_file new_file
					+etc/*) $SCRIPT_HOME/config_master.sh $old_file $new_file ;;
					# ./config_master.sh old_file new_file "remove"
					-etc/*) $SCRIPT_HOME/config_master.sh $old_file $new_file "remove" ;;
					etc/*) build_etc $new_file $old_file ;;
					esac

					;;
				*.xml)
					# Use backup_file replace old_file on module
					case $line in
                                        \!etc/*) \cp $patch_backup_dir/etc/$module/$(basename $old_file) $old_file ;;
					esac
					;;
				*.yml)
					;;
				esac

				echo "### $line" >>> $patch_rollback_file
				echo "\cp -R $patch_backup_dir/etc/$module/$(basename $old_file) $old_file" >> $patch_rollback_file

				[ $? -ne 0 ] && abort || ok	
				
				;;
			files/*|-files/*|+files/*|\!files/*)
				# change files.
				file_path=$(echo "$line" | sed -e 's/^files\///' -e 's/^\-files\///' -e 's/^\+files\///' -e 's/^\!files\///')
				case $module in
                                	Manager_Node_Engine|yxad.*|quartz_http_interface_jar|regd) dir1=$dataxone_basedir/$module ;;
					DataXone_Query_Engine/*) dir1=$dataxone_basedir/DataXone_Query_Engine ;;
                                	*) dir1=$(findWebapps "$module") ;;
                                esac

                                [ -z "$dir1" ] && dir1=$(find ${dataxone_basedir}/ -name "$module" -type d | egrep -v 'xpatch|Catalina|maven|Patch_')
				[ -z "$dir1" ] && abort

				echo "$module:$dir1" >> $SCRIPT_HOME/tmp/do_checksum.txt
				
				# webapps or dataxone_base ?
				dir0=$(dirname $dir1)

				case $line in
				files/*|-files/*)
					if [ $1 == "$ACTION_ARCHIVED" ] ; then
						mkdir -p $(dirname $patch_backup_dir/files/${file_path})
						[ $? -ne 0 ] && abort

						out "Backing up file $dir0/${file_path} to $patch_backup_dir/files/${file_path} ..."
						eval $(echo "\cp -R '$dir0/${file_path}' '$patch_backup_dir/files/${file_path}'")
						[ $? -ne 0 ] && abort || ok
					fi
					;;
				\!files/*)
					if [ $1 == "$ACTION_ARCHIVED" ] ; then
						mkdir -p $(dirname $patch_backup_dir/files/${module})
						[ $? -ne 0 ] && abort

						out "Backing up file $dir1 to $patch_backup_dir/files/${module} ..."
						eval $(echo "\cp -R '$dir1' '$patch_backup_dir/files/${module}'")
						[ $? -ne 0 ] && abort || ok
					fi
					;;
				esac	

				[ $1 == "$ACTION_ARCHIVED" ] && continue

				echo "### $line" >> $patch_rollback_file

				case $line in 
                                files/*)	
					# backup & cover file
					abs_file_path=${dir0}/${file_path}
					mkdir -p $(dirname $abs_file_path)
					[ $? -ne 0 ] && abort

					out "Updating file ${abs_file_path} with $base_path/${line} ..."
					eval $(echo "\cp -R '$base_path/${line}' '${abs_file_path}'")
					[ $? -ne 0 ] && abort || ok
					# build rollback scripts.
					echo "\rm '${abs_file_path}'" >> $patch_rollback_file
					echo "\cp -R '$patch_backup_dir/files/${file_path}' '${abs_file_path}' 2>/dev/null" >> $patch_rollback_file
					;;
				-files/*)
					# backup & remove file
					abs_file_path=${dir0}/${file_path}
                                        mkdir -p $(dirname $abs_file_path)
                                        [ $? -ne 0 ] && abort
	
					out "Removing file ${abs_file_path} ..."
					eval $(echo "\rm '${abs_file_path}'")
					[ $? -ne 0 ] && abort || ok
					
					# build rollback scripts.
					echo "\cp -R '$patch_backup_dir/files/${file_path}' '${abs_file_path}' 2>/dev/null" >> $patch_rollback_file
					;;
				+files/*)
					# add file
					mkdir -p ${dir1}
					[ $? -ne 0 ] && abort

					abs_file_path=${dir0}/${file_path}
					#dir2=$(dirname $file_path)
					out "Add file $base_path/files/${file_path} ..."
					eval $(echo "\cp -R '$base_path/files/${file_path}' '${abs_file_path}'")
					[ $? -ne 0 ] && abort || ok

					# build rollback scripts.
					echo "\rm '${abs_file_path}'" >> $patch_rollback_file

					;;
				\!files/*)
					mkdir -p $base_path/files/$module/.tmp
					cd $base_path/files/$module/.tmp
					[ $? -ne 0 ] && abort

					out "Overwrite file $dir0/$module ..."
					case $line in
					*.tar.gz) tar -zxf $base_path/files/${file_path} ;;
					*) ;;
					esac

					eval $(echo "\cp -R $base_path/files/$module/.tmp/*/* ${dir0}/$module")
					rm -rf $base_path/files/$module/.tmp
					[ $? -ne 0 ] && abort || ok
					echo "\rm -rf ${dir0}/$module" >> $patch_rollback_file
					echo "\cp -R '$patch_backup_dir/files/${module}' ${dir0}" >> $patch_rollback_file
					;;
				esac

				;;

			sqlfiles/*)

				unset module
				[ -n "$ARGV_PARAM_PATCH_SKIP_DB" ] && info "User Responded with: --skip-patch-db. Skip line ‘$base_path/$line’" && continue

				#sql_file=$(echo "$line" | sed 's/^sqlfiles\///')
				backupAndPatchDB $1 "$base_path/$line"

				;;
				

			esac

			[ -n "$script_file" ] && continue

			[ $1 == "$ACTION_APPLY" ] && echo "$module" >> $dataxone_inventory/$patch.tmp


		done < $patchset_dir/$patch/updates.lst


		if [ $1 == "$ACTION_ARCHIVED" ]; then
			info "Patch $patch: backed up successfully completed."
		elif [ $1 == "$ACTION_APPLY" ]; then


			bugs_fixed_list=$(cat $patchset_dir/$patch/bugs_fixed.txt 2>/dev/null | grep -v ^$ | awk '{print $1}' | tr '\n' ',')
			[ -z "$bugs_fixed_list" ] && bugs_fixed_list="<undefined>" 
			# update inventory.props files
			echo "" >> $inventory_file  && \
	                echo "$patch=$(cat $dataxone_inventory/$patch.tmp 2>/dev/null | grep -v ^$ | sort | uniq | tr '\n' ',')" >> $inventory_file && \
			echo "@${patch}=$(__now__) ###$bugs_fixed_list" >> $inventory_file  && \
        	        \rm $dataxone_inventory/$patch.tmp 2>/dev/null

			if [ -z "$ARGV_PARAM_PATCH_SKIP_CHECKSUM" ]; then
			unset module_path module checksum calc_checksum
			info "Calculating SHA256 checksum ..."
			# checksum
			for module_info in $(cat $SCRIPT_HOME/tmp/do_checksum.txt | sort | uniq)
			do
				module_path=$(echo "$module_info" | awk -F ':' '{print $2}')
				module=$(echo "$module_info" | awk -F ':' '{print $1}')
				checksum=$(egrep "SHA256 $module = " $patchset_dir/$patch/CHECKSUM | awk '{print $4}')
				case $module in
				autoMaticEngineBoot-*|permission|sbt_xcmp|dataXone_theme_dsg_project-*) 
					calc_checksum=$(calc_webapps_sha256sum $module_path) ;;
				DSGWEB|shell_mon)
					calc_checksum=$(calc_web_sha256sum $module_path) ;;
				esac

				if [ "$calc_checksum" != "$checksum" ]; then
					error "SHA256 checksum failed, patch=$checksum, installed=$calc_checksum, Please check again." && unset module_path module && info "Coming to the rollback process ..." && do_rollback Y
				else
					info "SHA256 checksum passed, patch=$checksum, installed=$calc_checksum."
				fi
					
			done
			unset module_path module checksum calc_checksum
			rm $SCRIPT_HOME/tmp/do_checksum.txt
			info "SHA256 checksum passed."
			fi

			info "Patch $patch: applied on $(date)."
        		info "Patch $patch successfully applied."
		fi

	done	

}

getVersion(){
	echo $ALLPY_VERSION
}

getPatchNumber(){
	echo "None"

}

mkdir -p $dataxone_inventory/logs
if [ -n "$__DATAXONE_XPATCH_ROLLBACK__"  ]; then
	logfile=$dataxone_inventory/logs/xpatch-rollback-$(date +'%Y-%m-%d_%H%M%S')
else
	logfile=$dataxone_inventory/logs/xpatch-apply-$(date +'%Y-%m-%d_%H%M%S')
fi
exec 1> >(tee -a /dev/tty > $logfile)
exec 2>&1

eval "$(grep ^PRETTY_NAME /etc/os-release)"

echo "
$(basename $0)	$version
DataXone interim Patch Installer version $version
copyright (c) 2024, dsgdata.com.  All rights reserved.

Dataxone base      	 : $dataxone_basedir
Dataxone version         : $(getVersion)
Installed Option modules :$option_modules
Inventory location 	 : $dataxone_inventory
Patch directory	   	 : $patchset_dir
Log file location  	 : $logfile

--------------------------------------------------------------------------------

Local Machine Information::
Hostname: $(uname -n)
Platform description: $(uname -s) $(uname -a | awk '{print $3}')
Pretty name: $PRETTY_NAME

"


if [ -z "$__DATAXONE_XPATCH_LS_INVENTORY__"  ]; then
if [ -n "$_ARGV_PARAM_DATAXONE_DB_HOST" -a -n "$db_host" -a "$_ARGV_PARAM_DATAXONE_DB_HOST" != "$db_host" ]; then
	error "Database configuration db_host is different, user response --mysql-host=$_ARGV_PARAM_DATAXONE_DB_HOST, but db_host=$db_host"
	abort
fi

if [ -n "$_ARGV_PARAM_DATAXONE_DB_USER" -a -n "$db_user" -a "$_ARGV_PARAM_DATAXONE_DB_USER" != "$db_user" ]; then
        error "Database configuration db_user is different, user response --mysql-user=$_ARGV_PARAM_DATAXONE_DB_USER, but db_user=$db_user"
        abort
fi
fi

if [ -n "$__DATAXONE_XPATCH_LS_INVENTORY__"  ]; then

	patched=0
	while read line
	do
		comment_line=$(echo $line | grep '^@')
		[ -z "$comment_line" ] && continue

		blank_line=$(echo $line | egrep '^[\s]*$' | wc -l)
                [ $blank_line -ne 0 ] && continue
	
		patch=$(echo "$line" | awk -F '=' '{print $1}' | sed 's/@//')
		patched_date=$(echo "$line" | sed -e "s/@${patch}=//" -e "s/###.*$//")
		bugs_fixed_list=$(echo "$line" | sed -e "s/^.*###[ ]*//" | awk -F ',' '{for(i=1;i<=NF;i++){if(i%8==0){s=s", "$i"\n"} else if(i%8==1){s=s"    "$i} else {s=s", "$i}}}END{print s}' | sed 's/,[ ]*$//')
		
		
		echo "
Patch $patch :  applied on $patched_date
  Bugs fixed: (Visit https://docs.qq.com/sheet/DZnR2cHJkV3lBS1Va?tab=1f4woc)
$bugs_fixed_list
"
		patched=1

	done < $inventory_file	

	[ $patched -eq 0 ] && echo "There are no Interim patches installed in this DataXone Base."

	completed && exit 0
fi


# rollback to old version
do_rollback(){
	user_rsp=$1

	patchset_dir=$backup_dir

	[ $(ls $patchset_dir | wc -l) -eq 0 ] && echo "There are no Interim patches installed in this DataXone Base."  && completed && exit 0

	for patch in $(ls -l $patchset_dir | sort -k9r | grep ^d | awk '{print $NF}'); do

		# User Responded...
                if [ -z "$user_rsp" ]; then
                        try_times=0
                        until [ $try_times -ge 3 ]; do
                                echo "Is the local system ready for rolling back? [y|n] "
                                read user_rsp
                                echo "User Responded with: $user_rsp"
                                [ "n" == "$user_rsp" -o "N" == "$user_rsp" ] && completed && exit 0
				[ "y" == "$user_rsp" -o "Y" == "$user_rsp" ] && echo && break
                                try_times=$(expr $try_times + 1)
                        done
                fi
                [ "y" != "$user_rsp" -a "Y" != "$user_rsp" ] && echo -e "\nBye." && exit -1


		info "Rolling back $patchset_dir/$patch ..."
		if [ -f $patchset_dir/$patch/rollback.sh ]; then
			info "Find rollback file $patchset_dir/$patch/rollback.sh."
			printf "\x1b[32m"
			sh $patchset_dir/$patch/rollback.sh
			printf "\x1b[0m"
			info "Patch $patch successfully rollback."
		else
			warn "Rollback: $patchset_dir/$patch/rollback.sh: No such file"
		fi
		
		info "Removing patch $patch backup files ..."
		\rm -rf $patchset_dir/$patch
		info "Patch $patch backup files successfully removed."
		echo

		sed -i -e "/^${patch}=/d" -e "/^@${patch}=/d" $inventory_file

	done

	export ROLLBACK_VERSION=$(python $SCRIPT_HOME/json-search-master.py $web_base_path/static/globalParams.json version)
	echo
	echo "Successfully rollback to version: $ROLLBACK_VERSION"
	echo

	completed && exit 0
}

catalina_runner=$(ps -ef --cols 1024 | grep "$dataxone_basedir/" | grep -v grep | grep -v '/bin/bash' | wc -l)
if [ $catalina_runner -ne 0 ]; then
	echo "Following component are active:"
	echo "1) Java:"
	ps -ef --cols 1024 | grep "$dataxone_basedir/" | grep -v grep | egrep 'java.*-Dcatalina.base=|java.*-DPath=' | awk '{for(i=1;i<=NF;i++)if($i~/^-Dcatalina.base=/||$i~/^-DPath=/){x=index($i,"=");print $2" "substr($i,x+1)}}'
	echo "2) Non-Java:"
	ps -ef --cols 1024 | grep "$dataxone_basedir/" | grep -v '/bin/bash' | egrep -v 'Dcatalina.base=.* -D|DPath=.* -D' | grep -v grep | awk '{print $2" "$8}'
	
	echo "
ApplySession failed during prerequisite checks: Prerequisite check active executables failed.
$(basename $0) failed with error code = -1
"
	exit -1
fi

print_install_recommend(){
	pack=$1
	# Centos 8/Rhel 8?
	case $pack in
	libncurses) pack=ncurses-compat-libs ;;
	python) pack="python3; cd /usr/bin && ln -s python3 python"
	esac
	echo "

>>We recommend install dependent first:
  
  Alpine Linux:
  # sudo apk add $pack

  Centos/Redhat:
  # sudo yum -y install $pack

  Debian:
  # sudo apt-get -y install $pack

  Fedora:
  # sudo dnf -y install $pack

  openSUSE:
  # sudo zypper install $pack
"

}


# Check dependency tools
# 
#
# --------------------------------
precheck_dept_tool(){
	f=$SCRIPT_HOME/tmp/0
	$MYSQL_HOME/bin/mysql --help --verbose 1>/dev/null 2>$f
	[ $? -ne 0 ] && lib=$(grep 'error while loading shared libraries' $f | awk -F ':' '{print $3}' | sed -e 's/.so.*$//' -e 's/[ ]*//') \
                && error "Precheck: $(cat $f)" && print_install_recommend $lib && abort
	$MYSQL_HOME/bin/mysqldump --help --verbose 1>/dev/null 2>$f
        [ $? -ne 0 ] && lib=$(grep 'error while loading shared libraries' $f | awk -F ':' '{print $3}' | sed -e 's/.so.*$//' -e 's/[ ]*//') \
                && error "Precheck: $(cat $f)" && print_install_recommend $lib && abort
	
	python -V >/dev/null 2>$f
	[ $? -ne 0 ] && error "Precheck: $(cat $f)" && print_install_recommend python && abort

	rn $f 2>/dev/null

}

[ -n "$__DATAXONE_XPATCH_ROLLBACK__" ] && do_rollback

precheck_dept_tool

applyPatch $ACTION_ARCHIVED

if [ $patch_count -ne 0 ]; then
	echo
	echo
	info "Applying patchset ..."
	#set -x
	applyPatch $ACTION_APPLY
fi


completed
